//========================================
// This miniplug has a standalone version available in the plugin market.
// https://www.npmjs.com/package/@dgck81lnn/koishi-plugin-chatluna-plugin-xdi8
//========================================

import { Schema } from "koishi"
import { ChatLunaPlugin } from "koishi-plugin-chatluna/services/chat"
import {
  fuzzyQuery,
  getMessageContent,
} from "koishi-plugin-chatluna/utils/string"

import { Tool } from "@langchain/core/tools"

export const name = "chatluna-plugin-xdi8"

export const inject = ["chatluna", "xdi8"]

export const Config = Schema.object({})

export function apply(ctx) {
  const plugin = new ChatLunaPlugin(ctx, {}, "plugin-xdi8", false)

  ctx.on("ready", async () => {
    ctx.logger.debug("registerTool hanzi_to_xdi8")
    plugin.registerTool("hanzi_to_xdi8", {
      selector(history) {
        ctx.logger.debug("select h2x")
        return history.slice(-10).some(item => {
          const content = getMessageContent(item.content)
          return fuzzyQuery(content, ["希顶", "xdi8", "shidinn"])
        })
      },
      authorization(session) {
        return ctx.filter(session)
      },
      createTool(params, session) {
        ctx.logger.debug("createTool h2x")
        return new HanziToXdi8Tool(ctx)
      },
    })

    ctx.logger.debug("registerTool xdi8_to_hanzi")
    plugin.registerTool("xdi8_to_hanzi", {
      selector(history) {
        ctx.logger.debug("select x2h")
        return history.slice(-10).some(item => {
          const content = getMessageContent(item.content)
          return fuzzyQuery(content, ["希顶", "xdi8", "shidinn"])
        })
      },
      authorization(session) {
        return ctx.filter(session)
      },
      createTool(params, session) {
        ctx.logger.debug("createTool x2h")
        return new Xdi8ToHanziTool(ctx)
      },
    })
  })
}

export class HanziToXdi8Tool extends Tool {
  name = "hanzi_to_xdi8"
  description =
  "Use this when you want to translate from Simplified Chinese to Shidinn.\n" +
  "This tool takes a piece of Chinese text and tries to convert it, " +
  "character by character, to Shidinn, and returns the result in Chat Alphabet. " +
  "Input must be in Simplified Chinese. Unmapped characters are kept as-is." +
  "When different translations are possible for a single Chinese character, " +
  "one is picked while a footnote indicating all possible translations for the character is provided. " +
  "Carefully examine each footnote. Replace the word with another possible translation " +
  "if the automatically picked one seems incorrect."

  constructor(ctx) {
    super({})
    this.ctx = ctx
  }

  async _call(input) {try{
    this.ctx.logger.debug("call h2x", input)
    let result = this.ctx.xdi8.hanziToXdi8Transcriber.transcribe(input, { ziSeparator: " ", alphaFilter: null })
    result = result.flatMap(seg => {
      if (Array.isArray(seg)) {
        let newSeg = seg.filter(alt => !alt.legacy)
        if (newSeg.length) seg = newSeg
        if (seg.length <= 1) return seg[0]?.content ?? "[ERROR]"
      }
      return [seg]
    })
    const alts = []
    const translation = result.map(seg => {
      if (typeof seg === "string") return seg.replace(
        /([。！，：；？…]+)(\s?)/g,
        (_, c, w) =>
          c.replace(
            /……|./g,
            (h) =>
              ({ "。": ".", "…": "...", "……": "..." }[h] ??
               String.fromCharCode(h.charCodeAt(0) - 0xfee0))
          ) + (w || " ")
      )
      if (Array.isArray(seg)) {
        const source = seg[0].content.map(seg => seg.h).join("")
        let seq = alts.findIndex(s => s.$ === source) + 1
        if (seq === 0) seq = alts.push(Object.assign(seg, { $: source }))
        return seg[0].content.map(seg => seg.v).join("") + "[^" + seq + "]"
      }
      return seg.v
    }).join("").trimEnd()
    const footnotes = alts.map((seg, i) => {
      const source = seg.$
      const alts = seg.map(alt => {
        let line = "* " + alt.content.map(seg => seg.v).join("")
        if (alt.note) line += " (" + alt.note.replace(/\n/g, ". ") + ")"
        return line
      })
      return (i + 1) + '. "' + source + '" possible translations:\n' + alts.join("\n")
    })
    let response = "Translation: " + translation
    if (alts.length) response += "\n\nFootnotes:\n" + footnotes.join("\n")
    this.ctx.logger.debug("response", response)
    return response
  }catch(e){this.ctx.logger.error(e);return "An unknown error occurred"}
  }
}

export class Xdi8ToHanziTool extends Tool {
  name = "xdi8_to_hanzi"
  description =
  "Use this when you want to understand or translate a piece of Shidinn text.\n" +
  "This tool takes a piece of Shidinn text and tries to convert it to Chinese characters " +
  "wherever possible. Unrecognized words are left as-is.\n" +
  "When different interpretations are possible, one is picked " +
  "while a footnote indicating all possible interpretations is provided. " +
  "Keep in mind that often some of the possible interpretations do not make sense. " +
  "Carefully examine each footnote. Replace words with another possible interpretation from the footnotes if the automatically picked one seems incorrect.\n" +
  "Results often include redundant spaces between characters, which you should remove before presenting your result."

  constructor(ctx) {
    super({})
    this.ctx = ctx
  }

  async _call(input) {try{
    this.ctx.logger.debug("call x2h", input)

    const irregularHanziCheckCache = Object.create(null)
    const isIrregularHanzi = (h) => {
      if (h in irregularHanziCheckCache) return irregularHanziCheckCache[h]
      const entries = this.ctx.xdi8.data.dict.filter(e => e.h === h)
      return irregularHanziCheckCache[h] = entries.every(e => e.xh === "-")
    }

    let result = this.ctx.xdi8.xdi8ToHanziTranscriber.transcribe(input, { ziSeparator: "", alphaFilter: null })
    result = result.flatMap(seg => {
      if (Array.isArray(seg)) {
        let newSeg = seg.filter(alt => {
          if (alt.exceptional && !alt.legacy) return false
          return alt.content.every(e => {
            if (e.x === "aho" && e.h !== "纟火") return false
            if (isIrregularHanzi(e.h)) return false
            return true
          })
        })
        if (newSeg.length) seg = newSeg
        if (seg.length <= 1) return seg[0]?.content ?? "[ERROR]"
      }
      return [seg]
    })
    const alts = []
    const translation = result.map(seg => {
      if (typeof seg === "string") return seg
      if (Array.isArray(seg)) {
        const source = seg[0].content.map(seg => seg.x).join("")
        let seq = alts.findIndex(s => s.$ === source) + 1
        if (seq === 0) seq = alts.push(Object.assign(seg, { $: source }))
        return seg[0].content.map(seg => seg.v).join("") + "[^" + seq + "]"
      }
      return seg.v
    }).join("").trimEnd()
    const footnotes = alts.map((seg, i) => {
      const source = seg.$
      const alts = seg.map(alt => {
        let line = "* " + alt.content.map(seg => {
          if (alt.content.length > 1) return `${seg.v} (${seg.x})`
          return seg.v
        }).join(" ")
        if (alt.note) line += " (" + alt.note.replace(/\n/g, ". ") + ")"
        return line
      })
      return (i + 1) + '. "' + source + '" possible interpretations:\n' + alts.join("\n")
    })
    let response = "Transliteration: " + translation
    if (alts.length) response += "\n\nFootnotes:\n" + footnotes.join("\n")
    this.ctx.logger.debug("response", response)
    return response
  }catch(e){this.ctx.logger.error(e);return "An unknown error occurred"}
  }
}
